package com.bourse.core.services.utils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * CacheManagerFactory. Merge several ehcache.xml configuration files.
 *
 */
public class CacheManagerFactoryBean implements FactoryBean, InitializingBean, DisposableBean {

	protected final Log logger = LogFactory.getLog(getClass());

	private String xml;

	private boolean shared = false;

	private String cacheManagerName;

	private CacheManager cacheManager;

	/**
	 * Set whether the EHCache CacheManager should be shared (as a singleton at the VM level) or independent (typically
	 * local within the application). Default is "false", creating an independent instance.
	 *
	 * @see net.sf.ehcache.CacheManager#create()
	 * @see net.sf.ehcache.CacheManager#CacheManager()
	 */
	public void setShared(boolean shared) {
		this.shared = shared;
	}

	/**
	 * Set the name of the EHCache CacheManager (if a specific name is desired).
	 *
	 * @see net.sf.ehcache.CacheManager#setName(String)
	 */
	public void setCacheManagerName(String cacheManagerName) {
		this.cacheManagerName = cacheManagerName;
	}

	public void setConfigLocation(Resource[] configLocations) {

		DocumentBuilder db;
		Document doc;
		Element element;

		// read 1st xml config file
		try {
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			db = dbf.newDocumentBuilder();
			doc = db.parse(configLocations[0].getInputStream());
			element = doc.getDocumentElement();
			element.normalize();
		} catch (Exception e) {
			String msg = "Error when reading the ehcache configuration file: '" + configLocations[0].getFilename()
					+ "'";
			logger.error(msg, e);
			throw new IllegalArgumentException("", e);
		}

		// add other caches from other ehcache.xml files if necessary
		try {
			if (configLocations.length > 1) {
				for (int i = 1; i < configLocations.length; i++) {
					Document docTmp = db.parse(configLocations[i].getInputStream());
					docTmp.getDocumentElement().normalize();
					NodeList nodesTmp = docTmp.getElementsByTagName("cache");
					for (int k = 0; k < nodesTmp.getLength(); k++) {
						Node node = nodesTmp.item(k);
						if (node.getNodeType() == Node.ELEMENT_NODE) {
							Node nodeTmp = doc.importNode(node, true);
							element.appendChild(nodeTmp);
						}
					}
				}
			}
		} catch (Exception e) {
			StringBuilder builder = new StringBuilder("Error when merging the ehcache configuration files:");
			for (Resource resource : configLocations) {
				builder.append(" '").append(resource.getFilename()).append("'");
			}
			String msg = builder.toString();
			logger.error(builder.toString(), e);
			throw new IllegalArgumentException(msg, e);
		}

		// convert the Document to a String
		try {
			DOMSource domSource = new DOMSource(doc);
			TransformerFactory tf = TransformerFactory.newInstance();
			Transformer transformer = tf.newTransformer();
			StringWriter sw = new StringWriter();
			StreamResult sr = new StreamResult(sw);
			transformer.transform(domSource, sr);
			xml = sw.toString();
		} catch (Exception e) {
			String msg = "Error when reading the ehcache configuration merge file";
			logger.error(msg, e);
			throw new IllegalArgumentException("", e);
		}
	}

	public void afterPropertiesSet() throws IOException, CacheException {
		logger.info("Initializing EHCache CacheManager");
		if (this.shared) {
			// Shared CacheManager singleton at the VM level.
			if (this.xml != null) {
				this.cacheManager = CacheManager.create(new ByteArrayInputStream(xml.getBytes("UTF-8")));
			} else {
				this.cacheManager = CacheManager.create();
			}
		} else {
			// Independent CacheManager instance (the default).
			if (this.xml != null) {
				this.cacheManager = new CacheManager(new ByteArrayInputStream(xml.getBytes("UTF-8")));
			} else {
				this.cacheManager = new CacheManager();
			}
		}

		// set default options
		for (String cacheName : this.cacheManager.getCacheNames()) {
			Ehcache cache = this.cacheManager.getEhcache(cacheName);

			// Enable statistics only if debug level is enables (better perf when statistics are disabled).
			if (logger.isDebugEnabled()) {
				cache.setStatisticsEnabled(true);
			}

			/* A blocking cache is not enough. A self populating cache is needed in order to
			 * feed again the non eternal values. Currently, use the selfPopulating annotation.
			 */
			// Set a blocking cache => avoid duplicate processing for concurrent operations
			// this.cacheManager.replaceCacheWithDecoratedCache(cache, new BlockingCache(cache));

		}

		if (this.cacheManagerName != null) {
			this.cacheManager.setName(this.cacheManagerName);
		}
	}

	public Object getObject() {
		return this.cacheManager;
	}

	public Class<?> getObjectType() {
		return (this.cacheManager != null ? this.cacheManager.getClass() : CacheManager.class);
	}

	public boolean isSingleton() {
		return true;
	}

	public void destroy() {
		logger.info("Shutting down EHCache CacheManager");
		this.cacheManager.shutdown();
	}

}
